package animator.phantom.controller;

/*
    Copyright Janne Liljeblad

    This file is part of Phantom2D.

    Phantom2D is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Phantom2D is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Phantom2D.  If not, see <http://www.gnu.org/licenses/>.
*/

import animator.phantom.gui.*;
import animator.phantom.gui.timeline.*;
import animator.phantom.renderer.*;
import animator.phantom.renderer.param.*;
import animator.phantom.project.*;
import animator.phantom.undo.*;
import animator.phantom.xml.PhantomXML;
import java.util.Vector;
import java.util.Collections;

//--- Handles timeline logic and state.
public class TimeLineController
{
	//--- The frame that is target of time editor operations and start point for playback etc..
	private static int currentFrame = 0;
	//--- First frame of displayed timeline view.
	private static int timeLinePosition = 0;
	//--- Values in float for pixels per frame in different scales.
	private static float[] frameMultipliers;
	//--- Params for creating frame multipliers.
	public static int SCALES_COUNT = 6;
	//--- The scale timeline is being displayed curretly
	private static int timeLineScale = SCALES_COUNT - 1;
	//--- Tells how many pix per frame when zoomed closet.
	private static final int PIXELS_PER_FRAME_IN_MAX_SCALE = 30;
	//--- User defined marks
	private static Vector<Integer> marks = new Vector<Integer>();
	//--- Clips in timeline editor, marked by iops they contain.
	private static Vector<ImageOperation> timelineClips = new Vector<ImageOperation>();
	//--- Selected timeline clips, marked by iops they contain.
	private static Vector<ImageOperation> selectedClips = new Vector<ImageOperation>();

	//--- INIT METHODS
	//--- Sets timeline values to original values.
	public static void reset()
	{
		timeLineScale = SCALES_COUNT - 1;
		timeLinePosition = 0;
		currentFrame = 0;
		frameMultipliers = null;
		marks = new Vector();
		selectedClips = new Vector<ImageOperation>();
		timelineClips = new Vector<ImageOperation>();
		frameMultipliers = null;
	}
	//--- Timeline init for project. Frame multipliers depend on project length.
	public static void init()
	{
		calculateFrameMultipliers();
		setMarks( PhantomXML.getMarks() );
	}

	//----------------------------------------------- FRAME
	//--- Current focus frame on the timeline.
	public static int getCurrentFrame(){ return currentFrame; }
	//--- Convenience method
	public static void changeCurrentFrame( int change, Object source ){ setCurrentFrame( currentFrame + change, source ); }
	//--- Setting current frame. This updates timeline editor gui too.
	//--- Updating other current frame depending gui done in UpdateController.
	public static void setCurrentFrame( int frame, Object source )
	{ 
		currentFrame = getBoundSafeFrame( frame );

		//--- Displays timocode in both clip and keyframe editor.
		for( TCPanel tcPanel: GUIComponents.tcPanels )
		{
			tcPanel.displayCurrentFrame( currentFrame );
			tcPanel.repaint();
		}
 		for( TimeLineDisplayPanel scaleDisp :  GUIComponents.timeLineScaleDisplays )
		{
			if( scaleDisp != source ) scaleDisp.repaint();
		}

		GUIComponents.normTlineDisp.setFrame( frame );
	}
	//--- Moves current frame to frame that has the previous key frame in given parameter.
	public static void  moveCurrentFrameToPreviousKeyFrame( KeyFrameParam param )
	{
		int moveToFrame = getPreviousKeyFrameFromCurrent( param );
		if( moveToFrame != -1 ) setCurrentFrame( moveToFrame, null );
	}
	//--- Returns frame that has the previous key frame in given parameter.
	public static int getPreviousKeyFrameFromCurrent( KeyFrameParam param )
	{
		//--- Get key frames and the frame that iop starts from.
		Vector <AnimationKeyFrame> keyFrames = param.getKeyFrames();
		ImageOperation iop = param.getIOP();
		int iopBeginFrame = iop.getBeginFrame();

		//--- Look for previous keyframe.
		int moveToFrame = -1;
		boolean found = false;
		int lastCheckedFrame = -1;
		
		//--- Handle case: current frame between some keyframes.
		for( AnimationKeyFrame keyFrame: keyFrames )	
		{
			int movieFrame = iopBeginFrame + keyFrame.getFrame();
			if( movieFrame >= currentFrame && 
				lastCheckedFrame < currentFrame 
				&& found == false )
			{
				
				moveToFrame = lastCheckedFrame;
				found = true;
			}
			lastCheckedFrame = movieFrame;
		}

		//--- Handle case: currentFrame > lastkeyframe.getFrame()
		if( moveToFrame == -1 )
		{
			int lastKeyFrame = keyFrames.lastElement().getFrame() + iopBeginFrame;
			if( currentFrame > lastKeyFrame ) moveToFrame = lastKeyFrame;
		}

		return moveToFrame;
	}
	//--- Moves current frame to the frame that has the next keyframe in given parameter.
	public static void moveCurrentFrameToNextKeyFrame( KeyFrameParam param )
	{
		int moveToFrame = getNextKeyFrameFromCurrent( param );
		if( moveToFrame != -1 ) setCurrentFrame( moveToFrame, null );
	}
	//--- Returns next keyframe in param after current
	private static int getNextKeyFrameFromCurrent( KeyFrameParam param )
	{
		//--- Get key frames and the frame that iop starts from.
		Vector <AnimationKeyFrame> keyFrames = param.getKeyFrames();
		ImageOperation iop = param.getIOP();
		int iopBeginFrame = iop.getBeginFrame();

		//--- Look for previous keyframe.
		int moveToFrame = -1;
		boolean found = false;
		int lastCheckedFrame = -1;
		
		//--- Handle case: current frame between some keyframes.
		for( AnimationKeyFrame keyFrame: keyFrames )	
		{
			int movieFrame = iopBeginFrame + keyFrame.getFrame();
			if( movieFrame > currentFrame && 
				lastCheckedFrame <= currentFrame 
				&& found == false )
			{
				
				moveToFrame = movieFrame;
				found = true;
			}
			lastCheckedFrame = movieFrame;
		}

		//--- Handle case: currentFrame < firtsKeyframe.getFrame()
		if( moveToFrame == -1 )
		{
			int firstKeyFrame = keyFrames.elementAt(0).getFrame() + iopBeginFrame;
			if( currentFrame < firstKeyFrame ) moveToFrame = firstKeyFrame;
		}

		return moveToFrame;
	}

	//--- Bounds check.
	private static int getBoundSafeFrame( int frame )
	{
		//--- bounds check
		if( frame < 0 ) 
			frame = 0;
		if( frame >= ProjectController.getLength() - 1 ) 
			frame = ProjectController.getLength() - 1;
		return frame;
	}

	//--------------------------------------------------------- SCALE
	//--- Set timeline position and order repaints.
	public static void setTimeLineScale( int scale, TimeLineSliders source )
	{
		timeLineScale = scale;
		scaleOrPosChanged( source );
	}
	//--- get timeline scale. NOTE: spelled with small l, too diffucult to change.
	public static int getTimelineScale(){ return timeLineScale; }
	//--- GUI components need acces to these to draw correctly
	public static float[] getFrameMultipliers(){ return frameMultipliers; }
	//--- Returns frame multiplier for index
	public static float getFrameMiltiplier( int index ){ return frameMultipliers[ index ]; }
	//--- Returns current scale multiplier
	public static float getCurrentScaleMultiplier(){ return frameMultipliers[ timeLineScale ]; }
	//--- Calculates frameMultpliers for certain length movie.
	private static void calculateFrameMultipliers()
	{
		//--- Get pixels per frame for scale that just displays whole movie.
		Float movieLengthF = new Float( ProjectController.getLength() );
		float scalewidth = AnimFrameGUIParams.getTimeEditRightColWidth();
		float pixelsPerFrameInMinScale = scalewidth /  movieLengthF.floatValue();

		Vector<Float> mults = new Vector<Float>();
		float scale = PIXELS_PER_FRAME_IN_MAX_SCALE;
		float stepMult = 0.66f;
		while( scale > pixelsPerFrameInMinScale )
		{
			mults.addElement( new Float(scale) );
			scale *= stepMult;
			System.out.println( scale );
		}
		mults.remove( mults.size() - 1 );
		mults.add( new Float(pixelsPerFrameInMinScale) );

		//--- Set scales parameters
		SCALES_COUNT = mults.size();
		timeLineScale = SCALES_COUNT - 1;

		//--- Create array
		frameMultipliers = new float[ SCALES_COUNT ];
		for( int i = 0; i < SCALES_COUNT; i++ )
			frameMultipliers[ i ] = mults.elementAt( i ).floatValue();
	}

	//----------------------------------------------- POSITION
	//--- Set timeline position and order repaint
	public static void setTimeLinePosition( int newPosition, TimeLineSliders source )
	{
		timeLinePosition = newPosition;
		scaleOrPosChanged( source );
	}
	//--- Get timeline position
	public static int getTimeLinePosition(){ return timeLinePosition; }

	//----------------------------------------------- SCALE AND POS UPDATE
	private static void scaleOrPosChanged( TimeLineSliders source )
	{
		GUIComponents.timeLineEditorPanel.scaleOrPositionChanged();

		for( TimeLineDisplayPanel scaleDisp : GUIComponents.timeLineScaleDisplays )
			scaleDisp.repaint();

		for( TimeLineSliders slidersPanel : GUIComponents.sliders )
			if( slidersPanel != source ) 
				slidersPanel.posOrScaleChanged( timeLinePosition, timeLineScale );
	}

	//-----------------------------------------------  EDITORS UPDATES
	public static void initClipEditorGUI()
	{
		GUIComponents.timeLineIOPColumnPanel.initGUI();
		GUIComponents.timeLineEditorPanel.initGUI();
	}
	public static void initClipsGUI()
	{
		GUIComponents.timeLineEditorPanel.initGUI();
	}
	public static void clipEditorRepaint()
	{
		GUIComponents.timeLineIOPColumnPanel.repaint();
		GUIComponents.timeLineEditorPanel.repaint();
	}
	public static void repaintTimeLineScaleDisplay()
	{
		for( TimeLineDisplayPanel scaleDisp :  GUIComponents.timeLineScaleDisplays )
			scaleDisp.repaint();
	}

	//-----------------------------------------------  MARKS AND MARKERS
	//--- Adds user defined mark.
	public static void addMark( Integer marker )
	{
		//--- 1 mark per frame, check.
		for( int i = 0; i < marks.size(); i++ )
		{
			if( marks.elementAt( i ).intValue() == marker.intValue() ) return;
		}
		//--- no marks in given frame, add.
		marks.add( marker );
		System.out.println( marks.size() );
	}
	//--- Removes mark from frame.
	public static void removeMarkerFromFrame( int frame )
	{
		int removeIndex = -1;
		for( int i = 0; i < marks.size(); i++ )
		{
			if( marks.elementAt(i ).intValue() == frame ) removeIndex = i;
		}
	
		if( removeIndex != -1 ) marks.remove( removeIndex );
	}
	//--- 
	public static Vector <Integer> getMarks(){ return marks; }
	public static void setMarks( Vector <Integer> newMarks){ marks = newMarks; }
	//--- Clears all marks
	public static void clearAllMarks(){ marks.clear();}


	//----------------------------------------------- TIMELINE CLIP ACTIONS
	//--- Clips being edited in timeline
	public static Vector<ImageOperation> getClips(){ return timelineClips; }
	//--- Add clips to timeline
	public static void addClips( Vector<ImageOperation> addClips )
	{
		for( ImageOperation clip : addClips )
			if( !clipsContain( clip ) )
				timelineClips.add( clip );
	}
	//--- Removes clip
	public static void removeClip( ImageOperation iop )
	{
		timelineClips.remove( iop );
	}

	//--- Return true if clips contain iop
	public static boolean clipsContain( ImageOperation iop )
	{
		return timelineClips.contains( iop );
	}

	public static void sendClipsToTimeline()
	{
		Vector<ImageOperation> sendClips = GUIComponents.renderFlowPanel.getAllSelectedIOPs();
		if( sendClips.size() == 0 ) return;

		ClipAddUndoEdit addEdit = new ClipAddUndoEdit( sendClips );
		addEdit.doEdit();
		PhantomUndoManager.addUndoEdit( addEdit );
	}

	public static void removeClipsCorrespondingtoNodes( Vector nodes )
	{
		for( int i = 0;  i < nodes.size(); i++ )
		{
			ImageOperation iop = ((RenderNode)nodes.elementAt( i )).getImageOperation();
			removeClip( iop );
		}
		initClipEditorGUI();
	}

	public static void removeClips( Vector<ImageOperation> removeClips )
	{
		for( ImageOperation clip : removeClips )
			removeClip( clip );
	}

	public static void targetIopChanged( ImageOperation iop )
	{
		//--- If no clip for new target clear clip editor selections.
		if( !clipsContain( iop ) )
		{
			selectedClips.clear();
			clipEditorRepaint();
			return;
		}
		//--- if ctrl is not pressed, set as only selected.
		if( !KeyStatus.ctrlIsPressed() ) 
			setAsSingleSelectedClip( iop );
		//--- if ctrl is pressed, add to selected.
		else
			addToSelectedClips( iop );

		clipEditorRepaint();
	}
	public static void setAsSingleSelectedClip( ImageOperation iop )
	{
		selectedClips.clear();
		selectedClips.add( iop );
	}
	public static void addToSelectedClips( ImageOperation iop )
	{
		selectedClips.add( iop );
	}
	public static void addAllToSelected(  Vector <ImageOperation> iops  )
	{
		selectedClips.addAll( iops );
	}
	public static void unselectAllClips()
	{
		selectedClips.clear();
	}
	public static boolean clipForIopIsSelected( ImageOperation iop )
	{
		return selectedClips.contains( iop );
	}
	public static void trimSelectedStartToCurrent()
	{
		for( ImageOperation clip : selectedClips )
		{
			TimeLineUndoEdit undoEdit = new TimeLineUndoEdit( clip );
			if( clip.isFreeLength() ) clip.setClipStartFrame( currentFrame );
			else
				if( clip.frameInProgramArea( currentFrame  ) )
					clip.setClipStartFrame( currentFrame );
			saveButtonEditUndo( undoEdit, clip );
		}

		//--- Repaint
		initClipEditorGUI();
	}

	public static void trimSelectedEndToCurrent()
	{
		for( ImageOperation clip : selectedClips )
		{
			TimeLineUndoEdit undoEdit = new TimeLineUndoEdit( clip );
			if( clip.isFreeLength() ) clip.setClipEndFrame( currentFrame - 1 );
			else
				if( clip.frameInProgramArea( currentFrame  ) )
					clip.setClipEndFrame( currentFrame - 1);
			saveButtonEditUndo( undoEdit, clip );
		}

		//--- Repaint
		initClipEditorGUI();
	}
	public static void moveClipStartToCurrent()
	{
		for( ImageOperation clip : selectedClips )
		{
			TimeLineUndoEdit undoEdit = new TimeLineUndoEdit( clip );
			clip.setBeginFrame( currentFrame );
			saveButtonEditUndo( undoEdit, clip );
		}

		//--- Repaint
		initClipEditorGUI();
	}
	public static void moveClipEndToCurrent()
	{
		for( ImageOperation clip : selectedClips )
		{
			TimeLineUndoEdit undoEdit = new TimeLineUndoEdit( clip );
			clip.setEndFrame( currentFrame - 1 );
			saveButtonEditUndo( undoEdit, clip );
		}

		//--- Repaint
		initClipEditorGUI();
	}

	private static void saveButtonEditUndo( TimeLineUndoEdit undoEdit, ImageOperation iop )
	{
		undoEdit.setAfterState( iop );
		PhantomUndoManager.addUndoEdit( undoEdit );
	}

	public static void deleteSelectedClips()
	{
		for( ImageOperation clip : selectedClips )
			removeClip( clip );

		//--- Repaint
		initClipEditorGUI();
	}

 	private static boolean frameIsClipStartFrame( int frame )
	{
		for( int i = 0; i < timelineClips.size(); i++ )
			if( timelineClips.elementAt( i ).getClipStartFrame() == frame ) return true;

		return false;
	}

	private static boolean frameIsMarkFrame(  int frame )
	{
		for( Integer m : marks )
			if( m.intValue() == frame ) return true;
		return false;
	}
	
	public static void toPreviousStop()
	{
		int prevParamFrame = -1;
		ImageOperation iop = GUIComponents.keyFrameEditPanel.getIOP();
		if( iop != null )
		{
			Vector<KeyFrameParam> params = iop.getKeyFrameParams();
			for( KeyFrameParam kfp : params )
			{
				int paramPrev = getPreviousKeyFrameFromCurrent( kfp );

				if( paramPrev != -1 && prevParamFrame == -1 )
					prevParamFrame = paramPrev;
				if( paramPrev != - 1 && paramPrev > prevParamFrame )
					prevParamFrame = paramPrev;
			}
		}

		int newFrame = currentFrame;
		boolean more = true;
		while( more )
		{
			newFrame--;
			if( frameIsClipStartFrame( newFrame ) ) more = false;
			else if( frameIsMarkFrame( newFrame ) ) more = false;
			else if( newFrame < 0 )
			{
				more = false;
				newFrame = 0;
			}
		}
		if( prevParamFrame != -1 && prevParamFrame > newFrame )
			setCurrentFrame( prevParamFrame, null );
		else
			setCurrentFrame( newFrame, null );
		UpdateController.updateCurrentFrameDisplayers( false );
	}

	public static void toNextStop()
	{
		int nextParamFrame = -1;
		ImageOperation iop = GUIComponents.keyFrameEditPanel.getIOP();
		if( iop != null )
		{
			Vector<KeyFrameParam> params = iop.getKeyFrameParams();
			for( KeyFrameParam kfp : params )
			{
				int paramNext = getNextKeyFrameFromCurrent( kfp );
				if( paramNext != -1 && nextParamFrame == -1 )
					nextParamFrame = paramNext;
				if( paramNext != - 1 && paramNext < nextParamFrame )
					nextParamFrame = paramNext;
			}
		}

		int newFrame = currentFrame;
		boolean more = true;
		while( more )
		{
			newFrame++;
			if( frameIsClipStartFrame( newFrame ) ) more = false;
			else if( frameIsMarkFrame( newFrame ) ) more = false;
			else if( newFrame == ProjectController.getLength() )
			{
				more = false;
				newFrame = ProjectController.getLength() - 1;
			}
		}

		if( nextParamFrame != -1 && nextParamFrame < newFrame )
			setCurrentFrame( nextParamFrame, null );
		else
			setCurrentFrame( newFrame, null );

		UpdateController.updateCurrentFrameDisplayers( false );
	}

	public static void toNextParamKeyFrame( KeyFrameParam p )
	{
		if( p == null )
		{
			setCurrentFrame( ProjectController.getLength() - 1, null );
			UpdateController.updateCurrentFrameDisplayers( false );
			return;
		}
		int paramNext = getNextKeyFrameFromCurrent( p );
		if( paramNext == -1 )
			setCurrentFrame( ProjectController.getLength() - 1, null );
		else
			setCurrentFrame( paramNext, null );

		UpdateController.updateCurrentFrameDisplayers( false );
	}

	public static void toPreviousParamKeyFrame( KeyFrameParam p )
	{
		if( p == null )
		{
			setCurrentFrame( 0, null );
			UpdateController.updateCurrentFrameDisplayers( false );
			return;
		}
		int paramNext = getPreviousKeyFrameFromCurrent( p );
		if( paramNext == -1 )
			setCurrentFrame( 0, null );
		else
			setCurrentFrame( paramNext, null );

		UpdateController.updateCurrentFrameDisplayers( false );
	}
	public static void nextFrame()
	{
		TimeLineController.setCurrentFrame( TimeLineController.getCurrentFrame() + 1, null );
		UpdateController.updateCurrentFrameDisplayers( false );
	}
	public static void previousFrame()
	{
		TimeLineController.setCurrentFrame( TimeLineController.getCurrentFrame() - 1, null );
		UpdateController.updateCurrentFrameDisplayers( false );
	}

	public static void moveSelectedClipsUp()
	{
		Vector<ImageOperation> selectedInOrder = getSelectedInOrder();
		for( int i = 0; i < selectedInOrder.size(); i++ )
		{
			
			int index = timelineClips.indexOf( selectedInOrder.elementAt( i ) );
			if( index > 0 )
				index --;
			timelineClips.remove( selectedInOrder.elementAt( i ) );
			timelineClips.add( index, selectedInOrder.elementAt( i ) ); 
		}
		initClipEditorGUI();
	}

	public static void moveSelectedClipsDown()
	{
		Vector<ImageOperation> selectedInOrder = getSelectedInOrder();
		Collections.reverse(selectedInOrder);
		for( int i = 0; i < selectedInOrder.size(); i++ )
		{
			
			int index = timelineClips.indexOf( selectedInOrder.elementAt( i ) );
			if( index < timelineClips.size() - 1 )
				index ++;
			timelineClips.remove( selectedInOrder.elementAt( i ) );
			timelineClips.add( index, selectedInOrder.elementAt( i ) ); 
		}
		initClipEditorGUI();
	}

	public static Vector<ImageOperation> getSelectedInOrder()
	{
		Vector<ImageOperation> selectedInOrder = new Vector<ImageOperation>();
		for( int i = 0; i < timelineClips.size(); i++ )
		{
			if( selectedClips.contains( timelineClips.elementAt( i )) )
				selectedInOrder.add( timelineClips.elementAt( i ) );
		}
		
		return selectedInOrder;
	}

	//----------------------------------------------------------------- LOCK
	public static void lockSelected()
	{
		for( ImageOperation clip : selectedClips )
			clip.setLocked( clip.getLocked() == false );//flip state
		clipEditorRepaint();
	}

}//end class
